/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "cmsis_os2.h"
#include "los_sem.h"
#include "los_interrupt.h"
#include "los_queue.h"

#include "os_error.h"
#include "jiffies.h"
#include "cpu.h"

#include "list.h"
#include "os_api.h"

static struct list_head head;

typedef struct {
    const char *name;
    u8 prio;
    u16 stack_size;
    u16 qsize;
    u8 *tcb_stk_q;
    int pid;
} task_info_type;

typedef struct {
    u32 argc: 8;
    u32 type: 24;
} taskq_head_type;

typedef struct {
    struct list_head entry;
    osThreadId_t thread_id;
    osMessageQueueId_t queue
} task_queue_type;

typedef struct {
    void (*function)(void *);
    void *parameter;
} osThreadWork_t;

#define configMAX_TASK_NAME_LEN 64
struct thread_parm {
    char thread_name[configMAX_TASK_NAME_LEN];
    void *parm;
    void (*thread_func)(void *);
    void (*thread_complete_cb)(void *);
    int (*rpc_condition)(void);
    void *thread_complete_cb_parm;
};

static void put_taskq(task_queue_type *tq)
{
    osKernelLock();
    list_add(&tq->entry, &head);
    osKernelUnlock();
}

static osMessageQueueId_t get_taskq(char *thread_name)
{
    char *name;
    task_queue_type *tq;

    osKernelLock();
    list_for_each_entry(tq, &head, entry) {
        name = osThreadGetName(tq->thread_id);
        if (name && thread_name) {
            if (!strcmp(name, thread_name)) {
                osKernelUnlock();
                return tq->queue;
            }
        }
    }
    osKernelUnlock();
    return NULL;
}

static void free_taskq(char *thread_name)
{
    task_queue_type *tq;
    osKernelLock();
    list_for_each_entry(tq, &head, entry) {
        if (!strcmp(osThreadGetName(tq->thread_id), thread_name)) {
            osKernelUnlock();
            osMessageQueueDelete(tq->queue);
            list_del(&tq->entry);
            free(tq);
            printf("free queue\n");
            return;
        }
    }
    osKernelUnlock();
}

void os_time_dly(int tick)
{
    osDelay(tick);
}

int os_sem_create(void *p_sem, int cnt)
{
    osSemaphoreId_t sem = osSemaphoreNew(255, cnt, NULL) ;
    if (!sem) {
        printf("%s  fail\n", __func__);
        return -1;
    }
    *(int *)p_sem = sem;
    return 0;
}

int os_sem_pend(void *p_sem, int timeout)
{
    int err = 0;
    osSemaphoreId_t sem = *((int *)p_sem);
    if (timeout == 0) {
        timeout = osWaitForever;
    }
    err = osSemaphoreAcquire(sem, timeout);
    return err == osOK ? 0 : OS_TIMEOUT;
}

int os_sem_post(void *p_sem)
{
    int err = 0;
    osSemaphoreId_t sem = *((int *)p_sem);
    err = osSemaphoreRelease(sem);
    return err == osOK ? 0 : -1;
}

int os_sem_del(void  *p_sem, int block)
{
    osSemaphoreId_t sem = *((int *)p_sem);
    (void)(block);
    osSemaphoreDelete(sem);

    *(int *)p_sem = NULL;
    return 0;
}

int os_sem_set(void *p_sem, u16 cnt)
{
    uint32_t uwIntSave;

    osSemaphoreId_t sem = *((int *)p_sem);
    if (OS_INT_ACTIVE) {
        printf("%s  %d err\n", __func__, __LINE__);
        return 0;
    }

    if (sem == NULL) {
        printf("%s  %d err\n", __func__, __LINE__);
        return 0;
    }

    uwIntSave = LOS_IntLock();
    ((LosSemCB *)sem)->semCount = cnt;
    LOS_IntRestore(uwIntSave);

    return osOK;
}

int os_sem_valid(void *p_sem)
{
    uint32_t uwIntSave;

    osSemaphoreId_t sem = *((int *)p_sem);
    if (OS_INT_ACTIVE) {
        printf("%s  %d err\n", __func__, __LINE__);
        return 0;
    }

    if (sem == NULL) {
        printf("%s  %d err\n", __func__, __LINE__);
        return 0;
    }

    uwIntSave = LOS_IntLock();

    if (((LosSemCB *)sem)->semStat == OS_SEM_USED) {
        LOS_IntRestore(uwIntSave);
        return 1;
    } else {
        LOS_IntRestore(uwIntSave);
        return 0;
    }
}

int os_mutex_create(void *p_mutex)
{
    osMutexId_t  mutex = osMutexNew(NULL) ;
    if (!mutex) {
        printf("%s  fail\n", __func__);
        return -1;
    }
    *(int *)p_mutex = mutex;

    return 0;
}

int os_mutex_del(void *p_mutex, int block)
{
    osMutexId_t mutex = * ((int *)p_mutex);

    (void)(block);
    osMutexDelete(mutex);
    *(int *)p_mutex = NULL;
    return 0;
}

int os_mutex_pend(void *p_mutex, int timeout)
{
    int err = 0;
    osMutexId_t mutex = * ((int *)p_mutex);
    if (timeout == 0) {
        timeout = osWaitForever;
    }
    err = osMutexAcquire(mutex, timeout);

    return err == osOK ? 0 : OS_TIMEOUT;
}

int os_mutex_post(void *p_mutex)
{
    int err = 0;
    osMutexId_t mutex = * ((int *)p_mutex);
    err = osMutexRelease(mutex);
    return err == osOK ? 0 : -1;
}

int os_mutex_valid(void *pevent)
{
    return 1;
}

int os_q_create(void *pevent, /* void **start, */u32 size)
{
    osMessageQueueId_t queue = osMessageQueueNew(size + 64, sizeof(UINT32), NULL);
    if (!queue) {
        printf("%s  fail\n", __func__);
        return -1;
    }

    *(int *)pevent = queue;
    return 0 ;
}

int os_q_valid(void *pevent)
{
    osMessageQueueId_t queue = *((int *)pevent);
    LosQueueCB *pstQueue = (LosQueueCB *)queue;

    uint32_t uwIntSave;

    if (OS_INT_ACTIVE) {
        return 0;
    }

    if (queue == NULL) {
        return 0;
    }

    uwIntSave = LOS_IntLock();

    if (pstQueue->queueState == OS_QUEUE_INUSED) {

        LOS_IntRestore(uwIntSave);
        return 1;
    } else {
        LOS_IntRestore(uwIntSave);
        return 0;
    }
}

int os_q_del(void *pevent, u8 opt)
{
    osMessageQueueId_t queue = *((int *)pevent);
    (void)(opt);
    osMessageQueueDelete(queue);

    *(int *)pevent = NULL;

    return 0;
}

int os_q_post(void *pevent, void *msg)
{
    int err;
    int pmsg = (int)msg;

    osMessageQueueId_t queue = *((int *)pevent);
    err = osMessageQueuePut(queue, &pmsg, 0, 0);

    return err == osOK ? 0 : -1;
}

int os_q_pend(void *pevent, int timeout, void *msg)
{
    int err = 0;
    osMessageQueueId_t queue = * ((int *)pevent);
    if (timeout == 0) {
        timeout = osWaitForever;
    }
    err = osMessageQueueGet(queue, msg, 0, timeout);
    return err == osOK ? 0 : OS_TIMEOUT;
}

u32 sys_timer_get_ms(void)
{
    return jiffies_to_msecs(jiffies);
}

void msleep(unsigned int ms)
{
    unsigned int t;

    t = ms / 10 + ((ms % 10) > 5 ? 1 : 0);
    osDelay(t == 0 ? 1 : t);
}

static void work(void *w)
{
    osThreadWork_t *wo = (osThreadWork_t *) w;
    wo->function(wo->parameter);
    osDelay(1000U);
    osThreadExit();
}

int thread_fork(const char *thread_name, int prio, int stk_size, u32 qsize, int *pid, void (*func)(void *), void *parm)
{
    osThreadId_t thread_id;
    osThreadAttr_t attr = {0};
    osThreadWork_t *w;

    osKernelLock();

    attr.name = thread_name;
    attr.attr_bits = 0U;
    attr.stack_size = (stk_size * 4);
    if (prio < osPriorityLow1) {

        attr.priority = osPriorityLow1;

    } else {
        attr.priority = prio;
    }

    w = (osThreadWork_t *)malloc(sizeof(osThreadWork_t));
    w->function = func;
    w->parameter = parm;

    printf(">>>thread create <%s> stack<%d> queue<%d> prio<%d>\n", thread_name, stk_size, qsize, attr.priority);

    task_queue_type *tq = NULL;
    u32 msg_size = 0, msg_count = 0;

    thread_id = osThreadNew(work, (void *)w, &attr);
    if (!thread_id) {
        printf("Falied to create thread!\n");
        osKernelUnlock();
        return -1;
    }

    if (pid) {
        *pid = thread_id;
    }

    if (qsize) {
        tq = malloc(sizeof(task_queue_type));
        msg_size = sizeof(u32);
        msg_count = qsize / msg_size;
        msg_count = (msg_count) ? msg_count : 1;
        tq->queue = osMessageQueueNew(msg_count, msg_size, NULL);
        tq->thread_id = thread_id;
        put_taskq(tq);
    }

    osKernelUnlock();
    return OS_NO_ERR;
}

void thread_kill(volatile int *pid, u8 mode)
{
    osStatus_t status;
    osThreadId_t id;
    char *thread_name;
    osMessageQueueId_t queue;

    osKernelLock();
    if (!pid) {
        osKernelUnlock();
        return;
    }

    id = (osThreadId_t) * pid ;

    thread_name = osThreadGetName(id);

    queue = get_taskq(thread_name);
    if (queue) {
        free_taskq(thread_name);
    }

    status = osThreadTerminate(id);

    if (status == osOK) {
    } else {
        printf("thread kill fail\n");
    }
    osKernelUnlock();
}

int *get_cur_thread_pid(void)
{
    return  LOS_CurTaskIDGet();
}

const char *os_current_task(void)
{
    return LOS_CurTaskNameGet();
}

static int thread_do(struct thread_parm *p_thread_parm)
{
    u32 uwRet;
    if (p_thread_parm->rpc_condition) {
        while (!p_thread_parm->rpc_condition()) {
            os_time_dly(50);
        }
    }

    p_thread_parm->thread_func(p_thread_parm->parm);
    if (p_thread_parm->thread_complete_cb) {
        p_thread_parm->thread_complete_cb(p_thread_parm->thread_complete_cb_parm);
    }

    free(p_thread_parm);

    p_thread_parm = NULL;
}

int thread_rpc(int (*condition)(void), const char *thread_name, int prio, int stk_size, u32 qsize, int *pid, \
    void (*func)(void *), void *parm, void (*thread_complete_cb)(void *), void *thread_complete_cb_parm)
{
    int ret = osOK;
    void *task;
    struct thread_parm *p_thread_parm = NULL;

    p_thread_parm = (struct thread_parm *)calloc(sizeof(struct thread_parm), 1);

    if (p_thread_parm == NULL) {
        puts("THREAD MALLOC FAIL\n");
        return -1;
    }

    if (thread_name == NULL) {
        static u8 rpc_cnt;
        strncpy_s(p_thread_parm->thread_name, configMAX_TASK_NAME_LEN - 1, "_rpc_%s_%x", os_current_task(), \
            ++rpc_cnt + rand32());
        thread_name = p_thread_parm->thread_name;
    } else {
        strncpy_s(p_thread_parm->thread_name, thread_name, sizeof(p_thread_parm->thread_name));
    }

    p_thread_parm->rpc_condition = condition;
    p_thread_parm->thread_complete_cb = thread_complete_cb;
    p_thread_parm->thread_complete_cb_parm = thread_complete_cb_parm;
    p_thread_parm->thread_func = func;
    p_thread_parm->parm = parm;

    osThreadAttr_t attr = {0};

    attr.name = p_thread_parm->thread_name;
    attr.stack_size = (stk_size * 4);
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;

    if (prio < osPriorityLow1) {
        attr.priority = osPriorityLow1 + 1;
    } else {
        attr.priority = prio;
    }

    if (pid) {
        *pid = osThreadNew(thread_do, (void *)p_thread_parm, &attr);
        if (*pid == NULL) {
            printf("Falied to create thread!\n");
            return -1;
        }
        printf("creat <%s> stack<%d> prio<%d> pid<%d>\n", thread_name, stk_size, attr.priority, *pid);
    } else {
        osThreadNew(thread_do, (void *)p_thread_parm, &attr);
        printf("creat <%s> stack<%d> prio<%d>\n", thread_name, stk_size, attr.priority);
    }

    return ret;
}

u32 OSGetTime()
{
    return jiffies;
}

void os_adapter_init(void)
{
    INIT_LIST_HEAD(&head);
}

static int os_taskq_callback(int argc, int argv[])
{
    int ret;
    int arg_num = argv[2] & 0xff;
    int param_num = arg_num;

    if (argv[2] & BIT(8)) {
        param_num++;
    }
    if (argv[2] & BIT(9)) {
        param_num++;
    }

    if (argc > param_num + 2) {
        if (arg_num == 1) {
            ret = ((int (*)(int))argv[1])(argv[3]);
        } else if (arg_num == 2 && !(argv[2] & BIT(10))) {
            ret = ((int (*)(int, int))argv[1])(argv[3], argv[4]);
        } else {
            ret = ((int (*)(int, int, int *))argv[1])(argv[3], arg_num - 1, argv + 4);
        }
        if (argv[2] & BIT(8)) {
            arg_num++;
            *(int *)argv[2 + arg_num] = ret;
        }
        if (argv[2] & BIT(9)) {
            arg_num++;
            os_sem_post((void *)argv[2 + arg_num]);
        }
        return 1;
    }

    return 0;
}

int os_taskq_pend(const char *fmt, int *argv, int argc)
{
    taskq_head_type tq_head;
    osMessageQueueId_t queue;
    char *thread_name = osThreadGetName(osThreadGetId());

    osKernelLock();
    queue = get_taskq(thread_name);
    if (!queue) {
        printf("OS_Q_EMPTY\n");
        osKernelUnlock();
        return OS_Q_EMPTY;
    }

    osStatus_t st;
    st = osMessageQueueGet(queue, &tq_head, NULL, 0xFFFFFFFF);
    argv[0] = tq_head.type;

    for (u8 i = 0; i < tq_head.argc; i++) {
        st = osMessageQueueGet(queue, argv + i + 1, NULL, 0xFFFFFFFF);
    }

    osKernelUnlock();

    if ((argv[0] & 0xf00000) == Q_CALLBACK) {
        os_taskq_callback(tq_head.argc + 1, argv);
    }

    return OS_TASKQ;
}

int os_taskq_post_type(const char *name, int type, int argc, int *argv)
{
    taskq_head_type tq_head;
    osMessageQueueId_t queue;

    osKernelLock();
    queue = get_taskq(name);

    if (!queue) {
        printf("%s, queue is NULL\n", __FUNCTION__);
        return OS_Q_ERR;
    }

    if (osMessageQueueGetSpace(queue) < argc + 1) {
        printf("queue full");
        osKernelUnlock();
        return OS_Q_FULL;
    }

    osStatus_t st;
    tq_head.argc = argc;
    tq_head.type = type;
    st = osMessageQueuePut(queue, &tq_head, NULL, 0);

    for (u8 i = 0; i < argc; i++) {
        osMessageQueuePut(queue, argv + i, NULL, 0);
    }

    osKernelUnlock();
    return 0;
}

static task_info_type task_info_table[] = {
    {"sys_event",           11,      1024,   0     },
    {"bt_event",            15,     1024,   256   },
    {"btctrler",            11,      1024,   384   },
    {"btstack",             12,      1024,   384   },
    {0, 0},
};

const task_queue_type *get_task_info(const char *name)
{
    const task_info_type *p;

    for (p = task_info_table; p->name != NULL; p++) {
        if (!strcmp(p->name, name)) {
            return p;
        } else if (p->name[0] == '#' && p->name[1] == 'C') {
            if (!strcmp(p->name + 3, name)) {
                return p;
            }
        }
    }

    return NULL;
}

int task_create(void (*task)(void *p), void *p, const char *name)
{
    const task_info_type *info;

    info = get_task_info(name);

    if (!info) {
        printf("%s, no task info\n", __FUNCTION__);
        return -1;
    }

    return thread_fork(name, info->prio, info->stack_size, info->qsize, &info->pid, task, p);
}

int task_kill(const char *name)
{
    const task_info_type *info;

    info = get_task_info(name);

    if (!info) {
        return -1;
    }

    thread_kill(&info->pid, 0);

    return 0;
}

